﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using Tycho.Runtime;
using Tycho.Compiler;
using Tycho.Parser.Tokens;
using System.IO;
using System.ComponentModel;
using Tycho.Transactions;
using System.Transactions;

namespace Tycho.Peyote {
    /// <summary>
    /// Interaction logic for Prompt.xaml
    /// </summary>
    public partial class InteractiveResultGlyph {
        Environment Environment;
        private AnyObject InternalRuntimeValue;
        public override GlyphHandle GlyphHandle { get; set; }
        public string Source { get; private set; }
        public Brush SourceColour { get; private set; }

        public InteractiveResultGlyph (string source, Environment environment) {
            InitializeComponent ();

            Environment = environment;
            Source = source;
            DataContext = this;

            Evaluate ();
        }

        public override AnyObject RuntimeValue {
            get {
                return InternalRuntimeValue;
            }
        }

        public override void NotifyModifiedDependency (CircularReferenceVigil circularReferenceVigil) {
            try {
                TransactionalObjectManager tx = Environment.Transaction (
                    () => InternalRuntimeValue = Environment.TopLevel.Evaluate (Source)
                    );

                var modifiedObjects = tx.ModifiedObjects;

                // if we're pure, we can watch for updates
                // pure = no side effects
                bool isPure = modifiedObjects.Count == 0;
                if (isPure) {
                    Environment.EvaluationDependencyEngine.RegisterDependency (this, tx.TouchedObjects);
                }

                Environment.EvaluationDependencyEngine.NotifyModification (this, modifiedObjects, circularReferenceVigil);

                SetResult (InternalRuntimeValue, isPure);
            } catch (Exception e) {
                SetError (e);
            }
        }

        void Evaluate () {
            NotifyModifiedDependency (null);
        }

        public void SetError (Exception error) {
            if (error is ExitException) {
                Environment.Exit ();
            } else if (error is CompilationException) {
                CompilationException cerr = (CompilationException) error;
                ErrorBox.Document = BuildExceptionMessage (cerr);
            } else {
                ErrorBox.Document = new FlowDocument (new Paragraph (new Run (error.Message)));
                ErrorBox.Document.Blocks.Add (new Paragraph (new Run (error.StackTrace)));
            }

            ErrorBox.Visibility = Visibility.Visible;
            ButtonPanel.Visibility = Visibility.Visible;
        }

        FlowDocument BuildExceptionMessage (CompilationException cerr) {
            SourceLocation sloc = cerr.SourceLocation;
            Paragraph para = new Paragraph ();

            if (sloc.FileName != null) {
                para.Inlines.Add ("file: " + sloc.FileName + "\n");
            }

            if (sloc.LineStart != sloc.LineEnd) {
                StringReader source = new StringReader (sloc.Source);

                for (int n = 0; n < sloc.LineStart - 1; n++) {
                    source.ReadLine ();
                }

                Span highlighted = new Span ();
                highlighted.Background = (Brush) Resources ["ErrorHighlightColour"];

                for (int n = sloc.LineStart; n <= sloc.LineEnd; n++) {
                    highlighted.Inlines.Add (source.ReadLine () + "\n");
                }

                para.Inlines.Add (highlighted);
            } else {
                StringReader source = new StringReader (sloc.Source);

                for (int n = 0; n < sloc.LineStart - 1; n++) {
                    source.ReadLine ();
                }

                string line = source.ReadLine ();

                para.Inlines.Add (line.Substring (0, sloc.ColumnStart));

                Span highlighted = new Span ();
                highlighted.Background = (Brush) Resources ["ErrorHighlightColour"];
                highlighted.Inlines.Add (line.Substring (sloc.ColumnStart, sloc.ColumnEnd - sloc.ColumnStart));
                para.Inlines.Add (highlighted);

                para.Inlines.Add (line.Substring (sloc.ColumnEnd, line.Length - sloc.ColumnEnd) + "\n");
            }

            para.Inlines.Add (cerr.Message);

            return new FlowDocument (para);
        }

        public void SetResult (AnyObject result, bool isPure) {
            InternalRuntimeValue = result;
            SourceColour = isPure? Brushes.Lime: Brushes.Cyan;

            using (var tx = new TransactionScope ()) {
                ValueBox.Content = Environment.Presenter.Present (result);
            }

            ValueBox.Visibility = Visibility.Visible;
            ButtonPanel.Visibility = Visibility.Visible;
        }

        private void RemoveButton_Click (object sender, RoutedEventArgs e) {
            Environment.Layout.RemoveGlyph (GlyphHandle);
        }

        private void RecalculateButton_Click (object sender, RoutedEventArgs e) {
            Evaluate ();
        }
    }
}
